Sfoglia il codice sorgente

支持添加标题和日期贴图

lanxin 3 settimane fa
parent
commit
c18a0fb459

+ 7 - 1
.vscode/settings.json

@@ -1,3 +1,9 @@
 {
 {
-  "editor.defaultFormatter": "esbenp.prettier-vscode"
+  "editor.defaultFormatter": "esbenp.prettier-vscode",
+  "[javascript]": {
+    "editor.defaultFormatter": "HookyQR.beautify"
+  },
+  "[wxml]": {
+    "editor.defaultFormatter": "wechat.miniprogram.wxml-language-features"
+  }
 }
 }

BIN
assets/img/icon_colorPick.png


File diff suppressed because it is too large
+ 2 - 2
miniprogram_npm/eventemitter3/index.js


+ 177 - 0
miniprogram_npm/mini-color-picker/color-picker.js

@@ -0,0 +1,177 @@
+Component({
+  properties: {
+    initColor: {
+      type: String,
+      value:'rgb(255,0,0)'
+    },
+    maskClosable: {
+      type: Boolean,
+      value: true
+    },
+    mask: {
+      type: Boolean,
+      value: true
+    },
+    show: {
+      type: Boolean,
+      value: false
+    },
+  },
+  data: {
+
+  },
+  lifetimes: {
+    attached() {
+      let { initColor} = this.data;
+      this.setData({
+        hueColor: this.hsv2rgb((this.rgb2hsv(initColor)).h,100,100)
+      })
+    },
+    ready() {
+      
+      const $ = this.createSelectorQuery()
+      const target = $.select('.target')
+      target.boundingClientRect()
+      $.exec((res) => {
+        const rect = res[0]
+        if (rect) {
+          this.SV = {
+            W: rect.width - 28, //block-size=28
+            H: rect.height - 28,
+            Step: (rect.width - 28) / 100
+          }
+          let { h, s, v } = this.rgb2hsv(this.data.initColor)
+          // 初始化定位
+          this.setData({
+            hsv:{
+              h,s,v
+            },
+            x: Math.round(s * this.SV.Step),
+            y: Math.round((100-v )* this.SV.Step)
+          })
+        }
+      })
+    }
+  },
+  methods: {
+    onEnd() {
+      this.triggerEvent('changeColor', {
+        color: this.data.colorRes
+      })
+    },
+    changeHue: function (e) {
+      let hue = e.detail.value;
+      this.setData({
+        "hsv.h":hue,
+        hueColor: this.hsv2rgb(hue, 100, 100),
+        colorRes: this.hsv2rgb(hue, this.data.hsv.s, this.data.hsv.v)
+      })
+    },
+    changeSV: function (e) {
+      let {
+        x,
+        y
+      } = e.detail;
+      x = Math.round(x / this.SV.Step);
+      y = 100 - Math.round(y / this.SV.Step);
+      this.setData({
+        "hsv.s":x,
+        "hsv.v": y,
+        colorRes: this.hsv2rgb(this.data.hsv.h, x, y)
+      })
+    },
+    close: function close(e) {
+      if (this.data.maskClosable) {
+        this.setData({
+          show: false
+        });
+        this.triggerEvent('close');
+      }
+    },
+    preventdefault:function() {
+      
+    },
+    hsv2rgb: function (h, s, v) {
+      let hsv_h = (h / 360).toFixed(2);
+      let hsv_s = (s / 100).toFixed(2);
+      let hsv_v = (v / 100).toFixed(2);
+
+      var i = Math.floor(hsv_h * 6);
+      var f = hsv_h * 6 - i;
+      var p = hsv_v * (1 - hsv_s);
+      var q = hsv_v * (1 - f * hsv_s);
+      var t = hsv_v * (1 - (1 - f) * hsv_s);
+
+      var rgb_r = 0,
+        rgb_g = 0,
+        rgb_b = 0;
+      switch (i % 6) {
+        case 0:
+          rgb_r = hsv_v;
+          rgb_g = t;
+          rgb_b = p;
+          break;
+        case 1:
+          rgb_r = q;
+          rgb_g = hsv_v;
+          rgb_b = p;
+          break;
+        case 2:
+          rgb_r = p;
+          rgb_g = hsv_v;
+          rgb_b = t;
+          break;
+        case 3:
+          rgb_r = p;
+          rgb_g = q;
+          rgb_b = hsv_v;
+          break;
+        case 4:
+          rgb_r = t;
+          rgb_g = p;
+          rgb_b = hsv_v;
+          break;
+        case 5:
+          rgb_r = hsv_v, rgb_g = p, rgb_b = q;
+          break;
+      }
+
+      return 'rgb(' + (Math.floor(rgb_r * 255) + "," + Math.floor(rgb_g * 255) + "," + Math.floor(rgb_b * 255)) + ')';
+    },
+    rgb2hsv: function (color) {
+      let rgb = color.split(',');
+      let R = parseInt(rgb[0].split('(')[1]);
+      let G = parseInt(rgb[1]);
+      let B = parseInt(rgb[2].split(')')[0]);
+
+      let hsv_red = R / 255, hsv_green = G / 255, hsv_blue = B / 255;
+      let hsv_max = Math.max(hsv_red, hsv_green, hsv_blue),
+        hsv_min = Math.min(hsv_red, hsv_green, hsv_blue);
+      let hsv_h, hsv_s, hsv_v = hsv_max;
+
+      let hsv_d = hsv_max - hsv_min;
+      hsv_s = hsv_max == 0 ? 0 : hsv_d / hsv_max;
+
+      if (hsv_max == hsv_min) hsv_h = 0;
+      else {
+        switch (hsv_max) {
+          case hsv_red:
+            hsv_h = (hsv_green - hsv_blue) / hsv_d + (hsv_green < hsv_blue ? 6 : 0);
+            break;
+          case hsv_green:
+            hsv_h = (hsv_blue - hsv_red) / hsv_d + 2;
+            break;
+          case hsv_blue:
+            hsv_h = (hsv_red - hsv_green) / hsv_d + 4;
+            break;
+        }
+        hsv_h /= 6;
+      }
+      return {
+        h: (hsv_h * 360).toFixed(),
+        s: (hsv_s * 100).toFixed(),
+        v: (hsv_v * 100).toFixed()
+      }
+    },
+  }
+})

+ 3 - 0
miniprogram_npm/mini-color-picker/color-picker.json

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

+ 20 - 0
miniprogram_npm/mini-color-picker/color-picker.wxml

@@ -0,0 +1,20 @@
+<view class="dialog {{show ? 'dialog_show' : ''}}">
+  <view wx:if="{{mask}}" class="weui-mask" catchtap="close" catchtouchmove="close"></view>
+  <view class="weui-actionsheet {{show ? 'weui-actionsheet_toggle' : ''}}" catchtouchmove="preventdefault">
+    <view class="weui-half-screen-dialog__hd">
+      <view class="weui-half-screen-dialog__hd__side" bindtap="close">
+        <a class="weui-icon-btn">
+          <i class="weui-icon-close-thin"></i>
+        </a>
+      </view>
+      <view class="weui-half-screen-dialog__hd__main">
+        <strong class="weui-half-screen-dialog__title">请选择颜色</strong>
+        <view class="weui-half-screen-dialog__subtitle">可通过下方滑块颜色预览</view>
+      </view>
+    </view>
+    <movable-area class="target" style="background-color:{{hueColor}}">
+      <movable-view direction="all" bindchange="changeSV" x="{{x}}" y="{{y}}" animation="{{false}}" class="iconfont icon-ios-locate-outline" bindtouchend="onEnd"></movable-view>
+    </movable-area>
+    <slider bindchanging="changeHue" activeColor="transparent" backgroundColor="transparent" class="ribbon" max="360" value="{{hsv.h}}" block-color="{{colorRes}}" bindtouchend="onEnd" />
+  </view>
+</view>

File diff suppressed because it is too large
+ 152 - 0
miniprogram_npm/mini-color-picker/color-picker.wxss


File diff suppressed because it is too large
+ 2 - 2
miniprogram_npm/widget-ui/index.js


+ 5 - 0
node_modules/.package-lock.json

@@ -10,6 +10,11 @@
       "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
       "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
       "license": "MIT"
       "license": "MIT"
     },
     },
+    "node_modules/mini-color-picker": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmmirror.com/mini-color-picker/-/mini-color-picker-0.1.2.tgz",
+      "integrity": "sha512-ln0KRwzj8Ted8quhcaHiTa+cj/lDrfryscoN+TwnmOf1E7st3Ud+kadgviMuniEZhL3TDwFY3qa1Km/iDSiMug=="
+    },
     "node_modules/widget-ui": {
     "node_modules/widget-ui": {
       "version": "1.0.2",
       "version": "1.0.2",
       "resolved": "https://registry.npmmirror.com/widget-ui/-/widget-ui-1.0.2.tgz",
       "resolved": "https://registry.npmmirror.com/widget-ui/-/widget-ui-1.0.2.tgz",

+ 21 - 0
node_modules/mini-color-picker/LICENSE

@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2020 MakerGYT
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 123 - 0
node_modules/mini-color-picker/README.md

@@ -0,0 +1,123 @@
+# Mini-color-picker
+
+> 小程序拾色器(颜色选择器)组件,通过调色盘取色,用于用户自定义场景。
+
+[![](https://img.shields.io/npm/v/mini-color-picker.svg)](https://www.npmjs.com/package/mini-color-picker/)
+[![npm](https://img.shields.io/npm/dw/mini-color-picker)](https://www.npmjs.com/package/mini-color-picker)
+[![](https://img.shields.io/badge/basicLib->=2.2.3-brightgreen?logo=wechat)]()
+[![GitHub stars](https://img.shields.io/github/stars/MakerGYT/mini-color-picker?style=social)](https://github.com/MakerGYT/mini-color-picker/stargazers)
+
+
+![mini-color-picker](https://cdn.jsdelivr.net/gh/makergyt/mini-color-picker/screenshot/poster.png)
+
+## 特性:
+### 现有方案分析
+- [we-color-picker](https://github.com/KirisakiAria/we-color-picker)
+需注意组件定位,操作[不跟手不流畅](https://developers.weixin.qq.com/community/develop/doc/00084ae5e400a8ae58e78263553c06#0006ae9d7c0ea05fe298cc59e514),配置复杂。其定位会撑开原有页面,体验不佳。滑动距离按像素区分(固定),需考虑设备分辨率,不利于多端。
+- [PapaerPen](https://www.jianshu.com/p/989b580168cd)
+利用原有slider组件实现滑动选取,不限于设备分辨率。但需分三次操作,且在色相的选定上内部实现繁琐.并不是真正作为三个分量(HSV/HSB)去影响色值
+
+### 解决方案
+- 利用官方提供的slider实现选择色相,movable-view选择饱和度和明度,由于是官方基础组件,操作顺畅。
+- 在滑动区域的设定上,获取实际节点后使用占比来影响色值变化,无需考虑rpx转换。
+- 在操作流程上,限于手机操作区域,不能使用Popover,使用底部拉起弹窗,不影响原有页面,[重点突出](https://developers.weixin.qq.com/miniprogram/design/#%E9%87%8D%E7%82%B9%E7%AA%81%E5%87%BA)。
+- 在操作预览上,由于弹窗遮罩不可避免无法实时预览,采用色相滑块的颜色来实现预览。同时考虑了iphone-x的安全区域问题。
+
+## 使用效果
+### 截图
+
+![mini-color-picker](https://cdn.jsdelivr.net/gh/makergyt/mini-color-picker/screenshot/demo.png)
+![mini-color-picker](https://cdn.jsdelivr.net/gh/makergyt/mini-color-picker/screenshot/demo.gif)
+
+### 样例
+
+[在开发者工具中预览效果](https://developers.weixin.qq.com/s/YOF4QUmO7NmW)=>代码片段ID:`YOF4QUmO7NmW`
+
+![微信搜索选色器](https://cdn.jsdelivr.net/gh/makergyt/mini-color-picker/screenshot/search.png)
+## 安装使用
+### 1. 获取组件
+#### git
+可能不稳定,但包含最新功能更新
+```sh
+git clone https://github.com/MakerGYT/mini-color-picker.git
+```
+将项目中components/color-picker文件夹拷贝到组件路径下
+#### npm
+稳定
+```sh
+npm install mini-color-picker --save
+```
+使用npm包请参考[微信小程序官方文档](https://developers.weixin.qq.com/miniprogram/dev/devtools/npm.html)
+
+### 2. 引入组件
+在使用该组件的页面对应json文件中添加:
+```json
+{
+  "usingComponents": {
+    "color-picker":"/components/color-picker/color-picker" 
+  }
+}
+```
+如使用npm,
+点击开发者工具中的菜单栏:工具 --> 构建 npm;
+勾选“使用 npm 模块”选项(demo为构建后的结果)
+```json
+{
+  "usingComponents": {
+    "color-picker":"mini-color-picker/color-picker" 
+  }
+}
+```
+
+### 3. 使用组件
+参考[/pages](https://github.com/makergyt/mini-color-picker/tree/master/pages/index)
+```html
+<!-- index.wxml -->
+<view style="background:{{rgb}};width:100px;height:24px;" bindtap="toPick"></view>
+<color-picker bindchangeColor="pickColor" initColor="{{rgb}}" show="{{pick}}" />
+```
+```js
+Page({
+  data:{
+    rgb: 'rgb(0,154,97)',//初始值
+    pick: false
+  },
+  // 显示取色器
+  toPick: function () {
+    this.setData({
+      pick: true
+    })
+  },
+  //取色结果回调
+  pickColor(e) {
+    let rgb = e.detail.color;
+  },
+}) 
+```
+## 属性列表
+| 属性 |类型| 默认值|必填|说明|
+| -- | --|--|--|--|
+| show | Boolean | false | 是 |是否显示 |
+|initColor| String | rgb(255,0,0)| 是 |初始色,rgb表示|
+|mask | Boolean |true | 否 |是否显示背景蒙层|
+|maskClosable | Boolean | true | 否 |点击背景蒙层是否可以关闭 |
+|bindchangeColor|eventhandler| | 否 | 取色后的回调,event.detail = {color} |
+|bindclose|eventhandler||否| 点击背景蒙层关闭掉color-picker后触发的事件|
+
+## 开发
+**注意**:
+1. 目前没有双向绑定,即获取初始值后色值由用户选取得到,不应该由外部传入改变(那样也就失去了取色的意义)
+2. 可以在触控过程中通过对滑块的颜色实时预览色值,而在停止触控后将色值传入页面,是为了避免频繁setData引起阻塞.
+3. 外部与组件通信的数据格式是rgb,为了避免引入多种数据格式而导致实际场景代码冗余,开发者可自行按需转换,在公众号"MakerGYT"回复以下对应内容获取参考函数代码:
+- rgb2hex
+- rgb2hsv
+- rgb2cmyk
+- hex2rgb
+- hsv2rgb
+
+![MakerGYT公众号](https://cdn.blog.makergyt.com/images/landmark-wechat_official-qrcode.jpg)
+
+## 案例
+![小程序使用预览](https://cdn.blog.makergyt.com/mini/assets/poster-H.png)
+## License
+[MIT](https://github.com/MakerGYT/mini-color-picker/blob/master/LICENSE) © MakerGYT

+ 177 - 0
node_modules/mini-color-picker/components/color-picker/color-picker.js

@@ -0,0 +1,177 @@
+Component({
+  properties: {
+    initColor: {
+      type: String,
+      value:'rgb(255,0,0)'
+    },
+    maskClosable: {
+      type: Boolean,
+      value: true
+    },
+    mask: {
+      type: Boolean,
+      value: true
+    },
+    show: {
+      type: Boolean,
+      value: false
+    },
+  },
+  data: {
+
+  },
+  lifetimes: {
+    attached() {
+      let { initColor} = this.data;
+      this.setData({
+        hueColor: this.hsv2rgb((this.rgb2hsv(initColor)).h,100,100)
+      })
+    },
+    ready() {
+      
+      const $ = this.createSelectorQuery()
+      const target = $.select('.target')
+      target.boundingClientRect()
+      $.exec((res) => {
+        const rect = res[0]
+        if (rect) {
+          this.SV = {
+            W: rect.width - 28, //block-size=28
+            H: rect.height - 28,
+            Step: (rect.width - 28) / 100
+          }
+          let { h, s, v } = this.rgb2hsv(this.data.initColor)
+          // 初始化定位
+          this.setData({
+            hsv:{
+              h,s,v
+            },
+            x: Math.round(s * this.SV.Step),
+            y: Math.round((100-v )* this.SV.Step)
+          })
+        }
+      })
+    }
+  },
+  methods: {
+    onEnd() {
+      this.triggerEvent('changeColor', {
+        color: this.data.colorRes
+      })
+    },
+    changeHue: function (e) {
+      let hue = e.detail.value;
+      this.setData({
+        "hsv.h":hue,
+        hueColor: this.hsv2rgb(hue, 100, 100),
+        colorRes: this.hsv2rgb(hue, this.data.hsv.s, this.data.hsv.v)
+      })
+    },
+    changeSV: function (e) {
+      let {
+        x,
+        y
+      } = e.detail;
+      x = Math.round(x / this.SV.Step);
+      y = 100 - Math.round(y / this.SV.Step);
+      this.setData({
+        "hsv.s":x,
+        "hsv.v": y,
+        colorRes: this.hsv2rgb(this.data.hsv.h, x, y)
+      })
+    },
+    close: function close(e) {
+      if (this.data.maskClosable) {
+        this.setData({
+          show: false
+        });
+        this.triggerEvent('close');
+      }
+    },
+    preventdefault:function() {
+      
+    },
+    hsv2rgb: function (h, s, v) {
+      let hsv_h = (h / 360).toFixed(2);
+      let hsv_s = (s / 100).toFixed(2);
+      let hsv_v = (v / 100).toFixed(2);
+
+      var i = Math.floor(hsv_h * 6);
+      var f = hsv_h * 6 - i;
+      var p = hsv_v * (1 - hsv_s);
+      var q = hsv_v * (1 - f * hsv_s);
+      var t = hsv_v * (1 - (1 - f) * hsv_s);
+
+      var rgb_r = 0,
+        rgb_g = 0,
+        rgb_b = 0;
+      switch (i % 6) {
+        case 0:
+          rgb_r = hsv_v;
+          rgb_g = t;
+          rgb_b = p;
+          break;
+        case 1:
+          rgb_r = q;
+          rgb_g = hsv_v;
+          rgb_b = p;
+          break;
+        case 2:
+          rgb_r = p;
+          rgb_g = hsv_v;
+          rgb_b = t;
+          break;
+        case 3:
+          rgb_r = p;
+          rgb_g = q;
+          rgb_b = hsv_v;
+          break;
+        case 4:
+          rgb_r = t;
+          rgb_g = p;
+          rgb_b = hsv_v;
+          break;
+        case 5:
+          rgb_r = hsv_v, rgb_g = p, rgb_b = q;
+          break;
+      }
+
+      return 'rgb(' + (Math.floor(rgb_r * 255) + "," + Math.floor(rgb_g * 255) + "," + Math.floor(rgb_b * 255)) + ')';
+    },
+    rgb2hsv: function (color) {
+      let rgb = color.split(',');
+      let R = parseInt(rgb[0].split('(')[1]);
+      let G = parseInt(rgb[1]);
+      let B = parseInt(rgb[2].split(')')[0]);
+
+      let hsv_red = R / 255, hsv_green = G / 255, hsv_blue = B / 255;
+      let hsv_max = Math.max(hsv_red, hsv_green, hsv_blue),
+        hsv_min = Math.min(hsv_red, hsv_green, hsv_blue);
+      let hsv_h, hsv_s, hsv_v = hsv_max;
+
+      let hsv_d = hsv_max - hsv_min;
+      hsv_s = hsv_max == 0 ? 0 : hsv_d / hsv_max;
+
+      if (hsv_max == hsv_min) hsv_h = 0;
+      else {
+        switch (hsv_max) {
+          case hsv_red:
+            hsv_h = (hsv_green - hsv_blue) / hsv_d + (hsv_green < hsv_blue ? 6 : 0);
+            break;
+          case hsv_green:
+            hsv_h = (hsv_blue - hsv_red) / hsv_d + 2;
+            break;
+          case hsv_blue:
+            hsv_h = (hsv_red - hsv_green) / hsv_d + 4;
+            break;
+        }
+        hsv_h /= 6;
+      }
+      return {
+        h: (hsv_h * 360).toFixed(),
+        s: (hsv_s * 100).toFixed(),
+        v: (hsv_v * 100).toFixed()
+      }
+    },
+  }
+})

+ 3 - 0
node_modules/mini-color-picker/components/color-picker/color-picker.json

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

+ 20 - 0
node_modules/mini-color-picker/components/color-picker/color-picker.wxml

@@ -0,0 +1,20 @@
+<view class="dialog {{show ? 'dialog_show' : ''}}">
+  <view wx:if="{{mask}}" class="weui-mask" catchtap="close" catchtouchmove="close"></view>
+  <view class="weui-actionsheet {{show ? 'weui-actionsheet_toggle' : ''}}" catchtouchmove="preventdefault">
+    <view class="weui-half-screen-dialog__hd">
+      <view class="weui-half-screen-dialog__hd__side" bindtap="close">
+        <a class="weui-icon-btn">
+          <i class="weui-icon-close-thin"></i>
+        </a>
+      </view>
+      <view class="weui-half-screen-dialog__hd__main">
+        <strong class="weui-half-screen-dialog__title">请选择颜色</strong>
+        <view class="weui-half-screen-dialog__subtitle">可通过下方滑块颜色预览</view>
+      </view>
+    </view>
+    <movable-area class="target" style="background-color:{{hueColor}}">
+      <movable-view direction="all" bindchange="changeSV" x="{{x}}" y="{{y}}" animation="{{false}}" class="iconfont icon-ios-locate-outline" bindtouchend="onEnd"></movable-view>
+    </movable-area>
+    <slider bindchanging="changeHue" activeColor="transparent" backgroundColor="transparent" class="ribbon" max="360" value="{{hsv.h}}" block-color="{{colorRes}}" bindtouchend="onEnd" />
+  </view>
+</view>

File diff suppressed because it is too large
+ 152 - 0
node_modules/mini-color-picker/components/color-picker/color-picker.wxss


+ 24 - 0
node_modules/mini-color-picker/package.json

@@ -0,0 +1,24 @@
+{
+  "name": "mini-color-picker",
+  "version": "0.1.2",
+  "description": "小程序拾色器(颜色选择器)组件,通过调色盘取色,用于用户自定义场景",
+  "miniprogram": "components/color-picker",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/MakerGYT/mini-color-picker.git"
+  },
+  "keywords": [
+    "wechat",
+    "小程序",
+    "取色器",
+    "color",
+    "picker",
+    "component"
+  ],
+  "author": "MakerGYT",
+  "license": "MIT",
+  "bugs": {
+    "url": "https://github.com/MakerGYT/mini-color-picker/issues"
+  },
+  "homepage": "https://github.com/MakerGYT/mini-color-picker#readme"
+}

+ 6 - 0
package-lock.json

@@ -9,6 +9,7 @@
       "version": "1.0.0",
       "version": "1.0.0",
       "license": "ISC",
       "license": "ISC",
       "dependencies": {
       "dependencies": {
+        "mini-color-picker": "^0.1.2",
         "wxml-to-canvas": "^1.1.1"
         "wxml-to-canvas": "^1.1.1"
       }
       }
     },
     },
@@ -18,6 +19,11 @@
       "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
       "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
       "license": "MIT"
       "license": "MIT"
     },
     },
+    "node_modules/mini-color-picker": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmmirror.com/mini-color-picker/-/mini-color-picker-0.1.2.tgz",
+      "integrity": "sha512-ln0KRwzj8Ted8quhcaHiTa+cj/lDrfryscoN+TwnmOf1E7st3Ud+kadgviMuniEZhL3TDwFY3qa1Km/iDSiMug=="
+    },
     "node_modules/widget-ui": {
     "node_modules/widget-ui": {
       "version": "1.0.2",
       "version": "1.0.2",
       "resolved": "https://registry.npmmirror.com/widget-ui/-/widget-ui-1.0.2.tgz",
       "resolved": "https://registry.npmmirror.com/widget-ui/-/widget-ui-1.0.2.tgz",

+ 1 - 0
package.json

@@ -9,6 +9,7 @@
   "license": "ISC",
   "license": "ISC",
   "description": "",
   "description": "",
   "dependencies": {
   "dependencies": {
+    "mini-color-picker": "^0.1.2",
     "wxml-to-canvas": "^1.1.1"
     "wxml-to-canvas": "^1.1.1"
   }
   }
 }
 }

+ 455 - 129
pages/work/index.js

@@ -1,7 +1,14 @@
 // index.js
 // index.js
 // 获取应用实例
 // 获取应用实例
-import { VueLikePage } from "../../utils/page";
-import { CDN_URL, API_BASE_URL, VIDEO_BASE_URL, app } from "../../config/index";
+import {
+  VueLikePage
+} from "../../utils/page";
+import {
+  CDN_URL,
+  API_BASE_URL,
+  VIDEO_BASE_URL,
+  app
+} from "../../config/index";
 
 
 VueLikePage([], {
 VueLikePage([], {
   data: {
   data: {
@@ -39,9 +46,156 @@ VueLikePage([], {
     zoomScrollLeft: 0,
     zoomScrollLeft: 0,
     scaleOrientation: '',
     scaleOrientation: '',
     baseUniformScale: 1,
     baseUniformScale: 1,
-    confirmedIps: []
+    confirmedIps: [],
+    // 1:贴纸,2:标题,3:日期
+    tabIndex: 1,
+    rgb: 'rgba(13, 121, 217, 1)', //初始值
+    rgbIndex: 1, //0,1,2,3
+    pick: false,
+    titleDatas: [],
+    title: '',
+    //日期
+    pickerValue: [0, 0, 0], // 年、月、日的选中索引
+    years: [], // 年份数组
+    months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], // 月份数组
+    days: [], // 日期数组(根据年月动态生成)
+    selectedDate: '' // 最终选中的日期
   },
   },
   methods: {
   methods: {
+    loadDate() {
+      const now = new Date();
+      const currentYear = now.getFullYear();
+      const years = [];
+      for (let i = currentYear - 10; i <= currentYear + 10; i++) {
+        years.push(i);
+      }
+      this.setData({
+        years
+      });
+
+      // 2. 初始化当前日期(默认选中今天)
+      const currentMonth = now.getMonth() + 1; // 月份从0开始,需+1
+      const currentDay = now.getDate();
+
+      // 计算当前年/月/日在数组中的索引
+      const yearIndex = years.indexOf(currentYear);
+      const monthIndex = currentMonth - 1;
+
+      // 初始化日期数组(根据当前年月)
+      this.setDays(currentYear, currentMonth);
+
+      // 设置默认选中值
+      this.setData({
+        pickerValue: [yearIndex, monthIndex, currentDay - 1],
+        selectedDate: `${currentYear}-${this.formatNum(currentMonth)}-${this.formatNum(currentDay)}`
+      });
+    },
+
+    // 格式化数字(补0,比如1→01)
+    formatNum(num) {
+      return num < 10 ? `0${num}` : num;
+    },
+
+    // 根据年、月动态生成日期数组(处理2月、小月/大月)
+    setDays(year, month) {
+      // 计算当月最后一天
+      const lastDay = new Date(year, month, 0).getDate();
+      const days = [];
+      for (let i = 1; i <= lastDay; i++) {
+        days.push(i);
+      }
+      this.setData({
+        days
+      });
+    },
+
+    // 日期选择器滚动变化时触发
+    onDateChange(e) {
+      const [yearIndex, monthIndex, dayIndex] = e.detail.value;
+      const {
+        years,
+        months,
+        days
+      } = this.data;
+
+      // 获取选中的年、月、日
+      const selectedYear = years[yearIndex];
+      const selectedMonth = months[monthIndex];
+      const selectedDay = days[dayIndex];
+
+      // 重新计算日期数组(防止切换年月后,日期超出当月范围,比如31号切到小月)
+      this.setDays(selectedYear, selectedMonth);
+
+      // 更新选中日期和picker值(日期索引可能变化,需重新校准)
+      const newDayIndex = Math.min(dayIndex, this.data.days.length - 1);
+      this.setData({
+        pickerValue: [yearIndex, monthIndex, newDayIndex],
+        selectedDate: `${selectedYear}-${this.formatNum(selectedMonth)}-${this.formatNum(this.data.days[newDayIndex])}`
+      });
+    },
+    // 输入时实时更新
+    onTitleInput(e) {
+      this.setData({
+        title: e.detail.value
+      })
+      console.log(123, this.data.title)
+    },
+    saveTitle(e) {
+      const currentTitle = this.data.title.trim()
+
+      // 防空判断
+      if (!currentTitle) {
+        wx.showToast({
+          title: '请输入标题',
+          icon: 'none'
+        })
+        return
+      }
+      // 追加
+      this.setData({
+        titleDatas: [...this.data.titleDatas, currentTitle],
+        title: '',
+      })
+
+      this.selectIp(e)
+
+      console.log(this.data.titleDatas)
+    },
+    // 显示取色器
+    toPick: function () {
+      this.setData({
+        pick: !this.data.pick,
+        rgbIndex:5
+      })
+    },
+    //取色结果回调
+    pickColor(e) {
+      let rgb = e.detail.color;
+
+      this.setData({
+        rgb: rgb
+      })
+    },
+    //设置颜色
+    setColor(e) {
+      let rgb = e.currentTarget.dataset.rgb;
+      let index = e.currentTarget.dataset.index;
+      console.log(rgb)
+      this.setData({
+        rgb: rgb,
+        rgbIndex: index
+      })
+    },
+    // 切换tab
+    handleTabTap(e) {
+      const index = e.currentTarget.dataset.index;
+      console.log(index, '11111')
+      this.setData({
+          tabIndex: index
+        }
+
+      )
+    },
     zoom() {
     zoom() {
       this.setData({
       this.setData({
         isZoom: !this.data.isZoom,
         isZoom: !this.data.isZoom,
@@ -56,8 +210,13 @@ VueLikePage([], {
       });
       });
     },
     },
     onLoad: function (options) {
     onLoad: function (options) {
-      let { rdw, id, type, projectid } = options;
-      if(!projectid){
+      let {
+        rdw,
+        id,
+        type,
+        projectid
+      } = options;
+      if (!projectid) {
         projectid = 'ZHS2409020-1';
         projectid = 'ZHS2409020-1';
       }
       }
       this.initIpsList(projectid);
       this.initIpsList(projectid);
@@ -68,7 +227,9 @@ VueLikePage([], {
         link = `${VIDEO_BASE_URL}4dvedio/${rdw}.mp4`;
         link = `${VIDEO_BASE_URL}4dvedio/${rdw}.mp4`;
       } else {
       } else {
         link = `${VIDEO_BASE_URL}4dpic/${rdw}.jpg`;
         link = `${VIDEO_BASE_URL}4dpic/${rdw}.jpg`;
-        // link='https://4dkk.4dage.com/fusion/test/file/797098a37e0c4588ae88f2ec4b1f12df.png'
+        // test
+        // link = 'https://pic.616pic.com/phototwo/00/06/02/618e27a7290161785.jpg'
+        this.loadDate()
       }
       }
 
 
       this.setData({
       this.setData({
@@ -103,11 +264,20 @@ VueLikePage([], {
       });
       });
     },
     },
     cancel() {
     cancel() {
-      // wx.reLaunch({
-      //   url: "/pages/work/index",
-      // });
+      if (this.data.isEditing) {
+        this.setData({
+          isEditing: false,
+        });
+      } else {
+        // wx.reLaunch({
+        //   url: "/pages/work/index",
+        // });
+
+        wx.navigateBack();
+      }
+
+
 
 
-      wx.navigateBack();
     },
     },
     edit() {
     edit() {
       this.setData({
       this.setData({
@@ -115,8 +285,29 @@ VueLikePage([], {
       });
       });
     },
     },
 
 
+    _wrapText(ctx, text, maxWidth) {
+      const chars = text.split('');
+      const lines = [];
+      let currentLine = '';
+
+      for (let char of chars) {
+        const testLine = currentLine + char;
+        if (ctx.measureText(testLine).width <= maxWidth || currentLine === '') {
+          currentLine = testLine;
+        } else {
+          lines.push(currentLine);
+          currentLine = char;
+        }
+      }
+      if (currentLine) lines.push(currentLine);
+      return lines;
+    },
+
     downloadF(cb = () => {}) {
     downloadF(cb = () => {}) {
       let link = this.data.url_link,
       let link = this.data.url_link,
+
+
+
         m_type = "";
         m_type = "";
 
 
       if (this.data.type == "1") {
       if (this.data.type == "1") {
@@ -124,7 +315,6 @@ VueLikePage([], {
       } else {
       } else {
         m_type = "video";
         m_type = "video";
       }
       }
-
       wx.downloadFile({
       wx.downloadFile({
         url: link,
         url: link,
         success: (res) => {
         success: (res) => {
@@ -136,9 +326,9 @@ VueLikePage([], {
           //判断是否为数组
           //判断是否为数组
           let typeType =
           let typeType =
             Object.prototype.toString.call(res.header["Content-Type"]) ==
             Object.prototype.toString.call(res.header["Content-Type"]) ==
-            "[object String]"
-              ? res.header["Content-Type"]
-              : res.header["Content-Type"][0];
+            "[object String]" ?
+            res.header["Content-Type"] :
+            res.header["Content-Type"][0];
 
 
           //判断不是xml文件
           //判断不是xml文件
           if (typeType.indexOf(m_type) > -1) {
           if (typeType.indexOf(m_type) > -1) {
@@ -154,24 +344,41 @@ VueLikePage([], {
       });
       });
     },
     },
 
 
+    drawRoundRect(ctx, x, y, width, height, radius) {
+      ctx.beginPath();
+      // 左上圆角
+      ctx.moveTo(x + radius, y);
+      ctx.arcTo(x + width, y, x + width, y + height, radius);
+      // 右上圆角
+      ctx.arcTo(x + width, y + height, x, y + height, radius);
+      // 右下圆角
+      ctx.arcTo(x, y + height, x, y, radius);
+      // 左下圆角
+      ctx.arcTo(x, y, x + width, y, radius);
+      ctx.closePath(); // 闭合路径
+    },
+
     async generateImage() {
     async generateImage() {
-      wx.showLoading({ title: '生成中...', mask: true });
+      wx.showLoading({
+        title: '生成中...',
+        mask: true
+      });
       try {
       try {
         const query = wx.createSelectorQuery();
         const query = wx.createSelectorQuery();
         query.select('.w_video').boundingClientRect();
         query.select('.w_video').boundingClientRect();
 
 
         if (this.data.selectedIp) {
         if (this.data.selectedIp) {
-            query.select('.ip-overlay').boundingClientRect();
+          query.select('.ip-overlay').boundingClientRect();
         }
         }
         query.selectAll('.confirmed-overlay').boundingClientRect();
         query.selectAll('.confirmed-overlay').boundingClientRect();
-        
+
         const res = await new Promise(resolve => query.exec(resolve));
         const res = await new Promise(resolve => query.exec(resolve));
         const container = res[0];
         const container = res[0];
         const overlay = this.data.selectedIp ? res[1] : null;
         const overlay = this.data.selectedIp ? res[1] : null;
         const confirmedRects = this.data.selectedIp ? (res[2] || []) : (res[1] || []);
         const confirmedRects = this.data.selectedIp ? (res[2] || []) : (res[1] || []);
-        
+
         if (!container) throw new Error('Cannot find container');
         if (!container) throw new Error('Cannot find container');
-        
+
         let width = container.width;
         let width = container.width;
         let height = container.height;
         let height = container.height;
 
 
@@ -189,16 +396,16 @@ VueLikePage([], {
 
 
         // Limit canvas size to avoid incomplete rendering on some devices
         // Limit canvas size to avoid incomplete rendering on some devices
         const dpr = wx.getSystemInfoSync().pixelRatio;
         const dpr = wx.getSystemInfoSync().pixelRatio;
-        const maxCanvasSize = 4096; 
+        const maxCanvasSize = 4096;
         let scale = 1;
         let scale = 1;
-        
+
         if (width * dpr > maxCanvasSize || height * dpr > maxCanvasSize) {
         if (width * dpr > maxCanvasSize || height * dpr > maxCanvasSize) {
-            scale = Math.min(maxCanvasSize / (width * dpr), maxCanvasSize / (height * dpr));
+          scale = Math.min(maxCanvasSize / (width * dpr), maxCanvasSize / (height * dpr));
         }
         }
 
 
         const canvasWidth = width * scale;
         const canvasWidth = width * scale;
         const canvasHeight = height * scale;
         const canvasHeight = height * scale;
-        
+
         await this._resetWidget(canvasWidth, canvasHeight);
         await this._resetWidget(canvasWidth, canvasHeight);
         const widget = this.selectComponent('#widget');
         const widget = this.selectComponent('#widget');
 
 
@@ -217,31 +424,163 @@ VueLikePage([], {
                 <view class="container">
                 <view class="container">
                 <image class="bg" src="${bgUrl}"></image>
                 <image class="bg" src="${bgUrl}"></image>
                 ${(this.data.type != '0') ? `<image class="main" src="${mainUrl}"></image>` : ''}
                 ${(this.data.type != '0') ? `<image class="main" src="${mainUrl}"></image>` : ''}
-                </view>
+              </view>
               `;
               `;
         }
         }
 
 
         const style = {
         const style = {
-          container: { width: canvasWidth, height: canvasHeight, position: 'relative', overflow: 'hidden' },
-          bg: { width: canvasWidth, height: canvasHeight, position: 'absolute', left: 0, top: 0 },
-          main: { width: canvasWidth, height: canvasHeight, position: 'absolute', left: 0, top: 0 }
+          container: {
+            width: canvasWidth,
+            height: canvasHeight,
+            position: 'relative',
+            overflow: 'hidden'
+          },
+          bg: {
+            width: canvasWidth,
+            height: canvasHeight,
+            position: 'absolute',
+            left: 0,
+            top: 0
+          },
+          main: {
+            width: canvasWidth,
+            height: canvasHeight,
+            position: 'absolute',
+            left: 0,
+            top: 0
+          }
         };
         };
 
 
-        await widget.renderToCanvas({ wxml, style });
+
 
 
         const ctx = widget.ctx;
         const ctx = widget.ctx;
         const use2d = widget.data.use2dCanvas;
         const use2d = widget.data.use2dCanvas;
         const ratio = canvasWidth / container.width;
         const ratio = canvasWidth / container.width;
-
+        await widget.renderToCanvas({
+          wxml,
+          style
+        });
         // Draw confirmed overlays
         // Draw confirmed overlays
         for (let i = 0; i < this.data.confirmedIps.length; i++) {
         for (let i = 0; i < this.data.confirmedIps.length; i++) {
           const ov = this.data.confirmedIps[i];
           const ov = this.data.confirmedIps[i];
           const rect = confirmedRects[i];
           const rect = confirmedRects[i];
           if (!ov || !rect) continue;
           if (!ov || !rect) continue;
+          const cx = ((rect.left - container.left + rect.width / 2) + (this.data.isZoom ? this.data.zoomScrollLeft : 0)) * ratio;
+          const cy = (rect.top - container.top + rect.height / 2) * ratio;
+          const overlayWidth = rect.width * ratio;
+          const overlayHeight = rect.height * ratio;
+          // 贴纸
+          console.log(ov)
+          if (ov.typeIndex == 1) {
+            const stickerInfo = await new Promise((resolve, reject) => {
+              wx.getImageInfo({
+                src: ov.imgUrl,
+                success: resolve,
+                fail: reject
+              })
+            });
+
+            let imgToDraw = stickerInfo.path;
+            if (use2d) {
+              const canvas = widget.canvas;
+              const img = canvas.createImage();
+              await new Promise((resolve, reject) => {
+                img.onload = resolve;
+                img.onerror = reject;
+                img.src = stickerInfo.path;
+              });
+              imgToDraw = img;
+            }
+            ctx.save();
+            ctx.translate(cx, cy);
+            ctx.rotate(ov.rotate * Math.PI / 180);
+            ctx.scale(ov.scaleX, ov.scaleY);
+            ctx.drawImage(imgToDraw, -overlayWidth / 2, -overlayHeight / 2, overlayWidth, overlayHeight);
+            ctx.restore();
+
+          } else if (ov.typeIndex == 2 || ov.typeIndex == 3) {
+            const text = ov.typeIndex == 2 ? ov.title.trim() : ov.date.trim();
+            if (!text) continue;
+            console.log(text)
+            ctx.save();
+            ctx.translate(cx, cy);
+            ctx.rotate((ov.rotate || 0) * Math.PI / 180);
+            ctx.scale(ov.scaleX || 1, ov.scaleY || 1);
+
+            // ── 参数定义 ──
+            const fontSizeRpx = 40;
+            const fontSizePx = fontSizeRpx * ratio * (container.width / 750);
+
+            const maxWidth = canvasWidth;
+            const minWidth = 80 * ratio;
+            const minHeight = 45 * ratio;
+            const lineHeight = 30;
+            const paddingRpx = 10;
+            const padding = paddingRpx * ratio;
+            const borderRadius = 40 * ratio;
+
+            ctx.font = ` ${fontSizePx}px iconfont`;
+            ctx.fillStyle = ov.rgb || '#000000';
+            ctx.textAlign = 'center';
+            ctx.textBaseline = 'middle';
+
+            // 计算换行
+            let lines = [text];
+            let textWidth = ctx.measureText(text).width;
+            if (textWidth > maxWidth) {
+              lines = this._wrapText(ctx, text, maxWidth);
+              textWidth = Math.max(...lines.map(line => ctx.measureText(line).width));
+            }
+
+            // 应用 min-width
+            const contentWidth = Math.max(textWidth, minWidth);
+            // 计算总内容高度(文字行高总和)
+            const contentHeight = lines.length * lineHeight;
+            // 最终容器高度(文字高度 + 上下 padding + min-height)
+            const finalHeight = Math.max(contentHeight + padding, minHeight);
+
+            console.log(minHeight)
+
+            // 最终容器宽度(文字宽度 + 左右 padding)
+            const finalWidth = contentWidth + padding * 2;
+
+            // 绘制圆角矩形背景
+            ctx.fillStyle = 'rgba(156, 208, 255, 1)';
+            ctx.beginPath();
+            this.drawRoundRect(
+              ctx,
+              -finalWidth / 2,
+              -finalHeight / 2,
+              finalWidth,
+              finalHeight,
+              20 // 圆角半径
+            );
+            ctx.fill();
+
+            // ── 绘制文字(在背景之上) ──
+            ctx.fillStyle = ov.rgb || '#000000'; // 恢复文字颜色
+            let yOffset = -contentHeight / 2 + lineHeight / 2; // 文字从容器中间开始
+
+            for (const line of lines) {
+              ctx.fillText(line, 0, yOffset);
+              yOffset += lineHeight;
+            }
+
+            ctx.restore();
+          }
+
+
+          if (!use2d) {
+            await new Promise(resolve => ctx.draw(true, resolve));
+          }
+        }
+        // Draw active overlay if exists
+        if (this.data.selectedIp && overlay) {
+          const stickerUrl = this.data.selectedIp.imgUrl;
 
 
           const stickerInfo = await new Promise((resolve, reject) => {
           const stickerInfo = await new Promise((resolve, reject) => {
             wx.getImageInfo({
             wx.getImageInfo({
-              src: ov.imgUrl,
+              src: stickerUrl,
               success: resolve,
               success: resolve,
               fail: reject
               fail: reject
             })
             })
@@ -259,117 +598,89 @@ VueLikePage([], {
             imgToDraw = img;
             imgToDraw = img;
           }
           }
 
 
-          const cx = ((rect.left - container.left + rect.width / 2) + (this.data.isZoom ? this.data.zoomScrollLeft : 0)) * ratio;
-          const cy = (rect.top - container.top + rect.height / 2) * ratio;
-          const overlayWidth = rect.width * ratio;
-          const overlayHeight = rect.height * ratio;
-
-          ctx.save();
-          ctx.translate(cx, cy);
-          ctx.rotate(ov.rotate * Math.PI / 180);
-          ctx.scale(ov.scaleX, ov.scaleY);
-          ctx.drawImage(imgToDraw, -overlayWidth / 2, -overlayHeight / 2, overlayWidth, overlayHeight);
-          ctx.restore();
-
-          if (!use2d) {
-            await new Promise(resolve => ctx.draw(true, resolve));
-          }
-        }
-
-        // Draw active overlay if exists
-        if (this.data.selectedIp && overlay) {
-          const stickerUrl = this.data.selectedIp.imgUrl;
-          
-          const stickerInfo = await new Promise((resolve, reject) => {
-              wx.getImageInfo({
-                  src: stickerUrl,
-                  success: resolve,
-                  fail: reject
-              })
-          });
-          
-          let imgToDraw = stickerInfo.path;
-          if (use2d) {
-              const canvas = widget.canvas;
-              const img = canvas.createImage();
-              await new Promise((resolve, reject) => {
-                  img.onload = resolve;
-                  img.onerror = reject;
-                  img.src = stickerInfo.path;
-              });
-              imgToDraw = img;
-          }
-          
           const cx = ((overlay.left - container.left + overlay.width / 2) + (this.data.isZoom ? this.data.zoomScrollLeft : 0)) * ratio;
           const cx = ((overlay.left - container.left + overlay.width / 2) + (this.data.isZoom ? this.data.zoomScrollLeft : 0)) * ratio;
           const cy = (overlay.top - container.top + overlay.height / 2) * ratio;
           const cy = (overlay.top - container.top + overlay.height / 2) * ratio;
           const overlayWidth = overlay.width * ratio;
           const overlayWidth = overlay.width * ratio;
           const overlayHeight = overlay.height * ratio;
           const overlayHeight = overlay.height * ratio;
-          
+
           ctx.save();
           ctx.save();
           ctx.translate(cx, cy);
           ctx.translate(cx, cy);
           ctx.rotate(this.data.ipRotate * Math.PI / 180);
           ctx.rotate(this.data.ipRotate * Math.PI / 180);
           ctx.scale(this.data.ipScaleX, this.data.ipScaleY);
           ctx.scale(this.data.ipScaleX, this.data.ipScaleY);
           ctx.drawImage(imgToDraw, -overlayWidth / 2, -overlayHeight / 2, overlayWidth, overlayHeight);
           ctx.drawImage(imgToDraw, -overlayWidth / 2, -overlayHeight / 2, overlayWidth, overlayHeight);
           ctx.restore();
           ctx.restore();
-          
+
           if (!use2d) {
           if (!use2d) {
-              await new Promise(resolve => ctx.draw(true, resolve));
+            await new Promise(resolve => ctx.draw(true, resolve));
           }
           }
         }
         }
-        
-        const { tempFilePath } = await widget.canvasToTempFilePath();
-        
+
+        const {
+          tempFilePath
+        } = await widget.canvasToTempFilePath();
+
         // Save
         // Save
         wx.saveImageToPhotosAlbum({
         wx.saveImageToPhotosAlbum({
-            filePath: tempFilePath,
-            success: () => {
-                wx.showModal({
-                    title: "提示",
-                    content: "已保存到相册,快去分享吧",
-                    showCancel: false,
-                });
-            },
-            fail: (e) => {
-                if (!(e.errMsg.indexOf("cancel") > -1)) {
-                    wx.showModal({
-                        title: "提示",
-                        content: "保存失败,请检查是否开启相册保存权限",
-                        showCancel: false,
-                    });
-                }
+          filePath: tempFilePath,
+          success: () => {
+            wx.showModal({
+              title: "提示",
+              content: "已保存到相册,快去分享吧",
+              showCancel: false,
+            });
+          },
+          fail: (e) => {
+            if (!(e.errMsg.indexOf("cancel") > -1)) {
+              wx.showModal({
+                title: "提示",
+                content: "保存失败,请检查是否开启相册保存权限",
+                showCancel: false,
+              });
             }
             }
+          }
         });
         });
 
 
       } catch (e) {
       } catch (e) {
         console.error(e);
         console.error(e);
-        wx.showToast({ title: '生成失败', icon: 'none' });
+        wx.showToast({
+          title: '生成失败',
+          icon: 'none'
+        });
       } finally {
       } finally {
         wx.hideLoading();
         wx.hideLoading();
       }
       }
     },
     },
     async _resetWidget(width, height) {
     async _resetWidget(width, height) {
-      this.setData({ widgetVisible: false });
+      this.setData({
+        widgetVisible: false
+      });
       await new Promise(r => setTimeout(r, 50));
       await new Promise(r => setTimeout(r, 50));
-      this.setData({ canvasWidth: width, canvasHeight: height, widgetVisible: true });
+      this.setData({
+        canvasWidth: width,
+        canvasHeight: height,
+        widgetVisible: true
+      });
       await new Promise(r => setTimeout(r, 120));
       await new Promise(r => setTimeout(r, 120));
     },
     },
     onZoomScroll(e) {
     onZoomScroll(e) {
-      this.setData({ zoomScrollLeft: e.detail.scrollLeft || 0 });
+      this.setData({
+        zoomScrollLeft: e.detail.scrollLeft || 0
+      });
     },
     },
 
 
     saveAlbum() {
     saveAlbum() {
       let type = this.data.type;
       let type = this.data.type;
-      
+
       if (this.data.projectid == 'ZHS2409020-1' && type !== '0') {
       if (this.data.projectid == 'ZHS2409020-1' && type !== '0') {
-         if (this.data.selectedIp && !this.data.ipConfirmed) {
-           wx.showToast({
-             title: '请先确认标签',
-             icon: 'none'
-           })
-           return;
-         }
-         this.generateImage();
-         return;
+        if (this.data.selectedIp && !this.data.ipConfirmed) {
+          wx.showToast({
+            title: '请先确认标签',
+            icon: 'none'
+          })
+          return;
+        }
+        this.generateImage();
+        return;
       }
       }
 
 
       wx.showLoading({
       wx.showLoading({
@@ -393,8 +704,7 @@ VueLikePage([], {
             if (!(e.errMsg.indexOf("cancel") > -1)) {
             if (!(e.errMsg.indexOf("cancel") > -1)) {
               wx.showModal({
               wx.showModal({
                 title: "提示",
                 title: "提示",
-                content:
-                  "保存失败,请检查是否开启相册保存权限,可在「右上角」 - 「设置」里查看",
+                content: "保存失败,请检查是否开启相册保存权限,可在「右上角」 - 「设置」里查看",
                 showCancel: false,
                 showCancel: false,
               });
               });
             }
             }
@@ -418,9 +728,13 @@ VueLikePage([], {
 
 
       wx.request({
       wx.request({
         url: `${VIDEO_BASE_URL}project/4dage-sxb/${prjId}/config.json`,
         url: `${VIDEO_BASE_URL}project/4dage-sxb/${prjId}/config.json`,
-        success: ({ data: { title, ...rest } }) => {
-          this.setData( 
-            {
+        success: ({
+          data: {
+            title,
+            ...rest
+          }
+        }) => {
+          this.setData({
               info: rest,
               info: rest,
             },
             },
             () => {
             () => {
@@ -436,6 +750,7 @@ VueLikePage([], {
     selectIp(e) {
     selectIp(e) {
       const index = e.currentTarget.dataset.index;
       const index = e.currentTarget.dataset.index;
       const item = this.data.ipsImgList[index];
       const item = this.data.ipsImgList[index];
+      // 这里日期和标题选择的index都没用,但是也需要传,相当于限制了添加上限为ips数组长度
       if (!item) return;
       if (!item) return;
       if (this.data.selectedIp && !this.data.ipConfirmed) {
       if (this.data.selectedIp && !this.data.ipConfirmed) {
         this.setData({
         this.setData({
@@ -443,6 +758,7 @@ VueLikePage([], {
           selectedIp: item
           selectedIp: item
         });
         });
       } else {
       } else {
+
         this.setData({
         this.setData({
           selectedIpIndex: index,
           selectedIpIndex: index,
           selectedIp: item,
           selectedIp: item,
@@ -453,23 +769,26 @@ VueLikePage([], {
           positionInitialized: false,
           positionInitialized: false,
         }, () => {
         }, () => {
           this.getOverlayRect();
           this.getOverlayRect();
-          
           const query = wx.createSelectorQuery();
           const query = wx.createSelectorQuery();
           query.select('.w_video').boundingClientRect();
           query.select('.w_video').boundingClientRect();
           query.select('.ip-overlay').boundingClientRect();
           query.select('.ip-overlay').boundingClientRect();
           query.exec((res) => {
           query.exec((res) => {
-              const container = res[0];
-              const overlay = res[1];
-              if (container && overlay) {
-                  this.setData({
-                      ipLeft: overlay.left - container.left,
-                      ipTop: overlay.top - container.top,
-                      positionInitialized: true
-                  });
-              }
+            const container = res[0];
+            const overlay = res[1];
+            console.log(container, overlay, 'index')
+            if (container && overlay) {
+
+              this.setData({
+                ipLeft: overlay.left - container.left,
+                ipTop: overlay.top - container.top,
+                positionInitialized: true
+              });
+            }
           });
           });
         });
         });
       }
       }
+
+
     },
     },
 
 
     dragStart(e) {
     dragStart(e) {
@@ -488,7 +807,7 @@ VueLikePage([], {
       const touch = e.touches[0];
       const touch = e.touches[0];
       const dx = touch.clientX - this.data.dragStartX;
       const dx = touch.clientX - this.data.dragStartX;
       const dy = touch.clientY - this.data.dragStartY;
       const dy = touch.clientY - this.data.dragStartY;
-      
+
       this.setData({
       this.setData({
         ipLeft: this.data.startIpLeft + dx,
         ipLeft: this.data.startIpLeft + dx,
         ipTop: this.data.startIpTop + dy
         ipTop: this.data.startIpTop + dy
@@ -513,7 +832,7 @@ VueLikePage([], {
       const dx = touch.clientX - this.data.centerX;
       const dx = touch.clientX - this.data.centerX;
       const dy = touch.clientY - this.data.centerY;
       const dy = touch.clientY - this.data.centerY;
       const startAngle = Math.atan2(dy, dx) * 180 / Math.PI;
       const startAngle = Math.atan2(dy, dx) * 180 / Math.PI;
-      
+
       this.setData({
       this.setData({
         startRotateAngle: startAngle,
         startRotateAngle: startAngle,
         baseIpRotate: this.data.ipRotate
         baseIpRotate: this.data.ipRotate
@@ -525,10 +844,10 @@ VueLikePage([], {
       const dx = touch.clientX - this.data.centerX;
       const dx = touch.clientX - this.data.centerX;
       const dy = touch.clientY - this.data.centerY;
       const dy = touch.clientY - this.data.centerY;
       const currentAngle = Math.atan2(dy, dx) * 180 / Math.PI;
       const currentAngle = Math.atan2(dy, dx) * 180 / Math.PI;
-      
+
       const diff = currentAngle - this.data.startRotateAngle;
       const diff = currentAngle - this.data.startRotateAngle;
       let nextRotate = this.data.baseIpRotate + diff;
       let nextRotate = this.data.baseIpRotate + diff;
-      
+
       this.setData({
       this.setData({
         ipRotate: nextRotate
         ipRotate: nextRotate
       });
       });
@@ -550,9 +869,12 @@ VueLikePage([], {
       let nextScale = this.data.baseUniformScale + (-dy) * factor;
       let nextScale = this.data.baseUniformScale + (-dy) * factor;
       if (nextScale < 0.2) nextScale = 0.2;
       if (nextScale < 0.2) nextScale = 0.2;
       if (nextScale > 4) nextScale = 4;
       if (nextScale > 4) nextScale = 4;
-      this.setData({ ipScaleX: nextScale, ipScaleY: nextScale });
+      this.setData({
+        ipScaleX: nextScale,
+        ipScaleY: nextScale
+      });
     },
     },
-    
+
     rotateIp() {
     rotateIp() {
       // 兼容旧的点击事件,如果不需要可以删除,但保留也不会出错
       // 兼容旧的点击事件,如果不需要可以删除,但保留也不会出错
       if (!this.data.selectedIp) return;
       if (!this.data.selectedIp) return;
@@ -587,8 +909,12 @@ VueLikePage([], {
     confirmIp() {
     confirmIp() {
       if (!this.data.selectedIp) return;
       if (!this.data.selectedIp) return;
       const overlayItem = {
       const overlayItem = {
+        typeIndex: this.data.tabIndex, // 判断是贴图还是标题还是日期
         id: Date.now(),
         id: Date.now(),
+        rgb: this.data.rgb,
         imgUrl: this.data.selectedIp.imgUrl,
         imgUrl: this.data.selectedIp.imgUrl,
+        title: this.data.titleDatas[this.data.titleDatas.length - 1],
+        date: this.data.selectedDate,
         scaleX: this.data.ipScaleX,
         scaleX: this.data.ipScaleX,
         scaleY: this.data.ipScaleY,
         scaleY: this.data.ipScaleY,
         rotate: this.data.ipRotate,
         rotate: this.data.ipRotate,
@@ -607,4 +933,4 @@ VueLikePage([], {
       });
       });
     },
     },
   },
   },
-});
+});

+ 2 - 1
pages/work/index.json

@@ -1,5 +1,6 @@
 {
 {
   "usingComponents": {
   "usingComponents": {
-    "wxml-to-canvas": "wxml-to-canvas"
+    "wxml-to-canvas": "wxml-to-canvas",
+    "color-picker":"mini-color-picker/color-picker" 
   }
   }
 }
 }

+ 106 - 16
pages/work/index.wxml

@@ -12,23 +12,56 @@
         <text style="font-size: 31rpx;">{{isZoom ? '全图模式' : '大图模式'}}</text>
         <text style="font-size: 31rpx;">{{isZoom ? '全图模式' : '大图模式'}}</text>
       </view>
       </view>
     </view>
     </view>
-    <view wx:if="{{!isZoom}}" class="w_video" style="background-image: url({{cdn_url+info.resourceImg.bg}});{{info.resourceImg.style}}">
-      <video wx:if="{{type=='0'}}" style="opacity:{{loadCompele?1:0}};{{info.resourceImg.imgStyle}}" bindloadedmetadata="loadcompele" src="{{url_link}}" loop autoplay controls="{{false}}" enable-progress-gesture="{{false}}"></video>
-      <image wx:else style="opacity:{{loadCompele?1:0}};{{info.resourceImg.imgStyle}}" bindload="loadcompele" show-menu-by-longpress="true" src="{{url_link}}" mode="widthFix" />
+    <view wx:if="{{!isZoom}}" class="w_video"
+      style="background-image: url({{cdn_url+info.resourceImg.bg}});{{info.resourceImg.style}}">
+      <video wx:if="{{type=='0'}}" style="opacity:{{loadCompele?1:0}};{{info.resourceImg.imgStyle}}"
+        bindloadedmetadata="loadcompele" src="{{url_link}}" loop autoplay controls="{{false}}"
+        enable-progress-gesture="{{false}}"></video>
+      <image wx:else style="opacity:{{loadCompele?1:0}};{{info.resourceImg.imgStyle}}" bindload="loadcompele"
+        show-menu-by-longpress="true" src="{{url_link}}" mode="widthFix" />
       <block wx:if="{{projectid == 'ZHS2409020-1'}}">
       <block wx:if="{{projectid == 'ZHS2409020-1'}}">
-        <view wx:for="{{confirmedIps}}" wx:key="id" class="ip-overlay ip-overlay__confirmed confirmed-overlay" style="left:{{item.left}}px; top:{{item.top}}px; right:auto; bottom:auto;">
-          <image class="ip-main" src="{{item.imgUrl}}" style="transform:scale({{item.scaleX}}, {{item.scaleY}}) rotate({{item.rotate}}deg) translateZ(0); will-change: transform;" />
+        <view wx:for="{{confirmedIps}}" wx:key="id"
+          class="ip-overlay {{item.typeIndex==3 ? 'ip-overlay-title':item.typeIndex==2?'ip-overlay-title':'' }} ip-overlay__confirmed confirmed-overlay"
+          style="left:{{item.left}}px; top:{{item.top}}px; right:auto; bottom:auto;">
+          <!-- 贴图 -->
+          <image wx:if="{{item.typeIndex==1}}" class="ip-main" src="{{item.imgUrl}}"
+            style="transform:scale({{item.scaleX}}, {{item.scaleY}}) rotate({{item.rotate}}deg) translateZ(0); will-change: transform;" />
+          <!-- 标题 -->
+          <view wx:if="{{item.typeIndex==2}}" class="ip-title-main"
+            style="color:{{item.rgb}};transform:scale({{item.scaleX}}, {{item.scaleY}}) rotate({{item.rotate}}deg) translateZ(0); will-change: transform;"
+            catchtouchstart="dragStart" catchtouchmove="dragMove">{{item.title}}</view>
+            <!-- 日期 -->
+          <view wx:if="{{item.typeIndex==3}}" class="ip-title-main"
+            style="color:{{item.rgb}};transform:scale({{item.scaleX}}, {{item.scaleY}}) rotate({{item.rotate}}deg) translateZ(0); will-change: transform;"
+            catchtouchstart="dragStart" catchtouchmove="dragMove">{{item.date}}</view>
         </view>
         </view>
       </block>
       </block>
-      <view wx:if="{{selectedIp && projectid == 'ZHS2409020-1'}}" class="ip-overlay {{ipConfirmed ? 'ip-overlay__confirmed' : ''}}" style="opacity:{{positionInitialized?1:0}}; {{positionInitialized ? 'left:'+ipLeft+'px; top:'+ipTop+'px; right: auto; bottom: auto;' : ''}}">
-        <image class="ip-main" src="{{selectedIp.imgUrl}}" style="transform:scale({{ipScaleX}}, {{ipScaleY}}) rotate({{ipRotate}}deg) translateZ(0); will-change: transform;" catchtouchstart="dragStart" catchtouchmove="dragMove" />
-        <image class="ip-btn ip-btn__rotate" src="/assets/img/icon_rorate.png" mode="widthFix" catchtouchstart="rotateStart" catchtouchmove="rotateMove" />
-        <image class="ip-btn ip-btn__scale" src="/assets/img/icon_scale.png" mode="widthFix" catchtouchstart="scaleStart" catchtouchmove="scaleMove" />
+      <!-- 可以操作的情况 -->
+      <view wx:if="{{selectedIp && projectid == 'ZHS2409020-1'}}"
+        class="ip-overlay {{tabIndex==3 ? 'ip-overlay-title':tabIndex==2?'ip-overlay-title':'' }} {{ipConfirmed ? 'ip-overlay__confirmed' : ''}}"
+        style="opacity:{{positionInitialized?1:0}}; {{positionInitialized ? 'left:'+ipLeft+'px; top:'+ipTop+'px; right: auto; bottom: auto;' : ''}}">
+        <!-- 贴图 -->
+        <image wx:if="{{tabIndex==1}}" class="ip-main" src="{{selectedIp.imgUrl}}"
+          style="transform:scale({{ipScaleX}}, {{ipScaleY}}) rotate({{ipRotate}}deg) translateZ(0); will-change: transform;"
+          catchtouchstart="dragStart" catchtouchmove="dragMove" />
+        <!-- 标题 -->
+        <view wx:if="{{tabIndex==2}}" class="ip-title-main"
+          style="color:{{rgb}};transform:scale({{ipScaleX}}, {{ipScaleY}}) rotate({{ipRotate}}deg) translateZ(0); will-change: transform;"
+          catchtouchstart="dragStart" catchtouchmove="dragMove">{{titleDatas[titleDatas.length-1]}}</view>
+        <!-- 日期 -->
+        <view wx:if="{{tabIndex==3}}" class="ip-title-main"
+          style="color:{{rgb}};transform:scale({{ipScaleX}}, {{ipScaleY}}) rotate({{ipRotate}}deg) translateZ(0); will-change: transform;"
+          catchtouchstart="dragStart" catchtouchmove="dragMove">{{selectedDate}}</view>
+        <image class="ip-btn ip-btn__rotate" src="/assets/img/icon_rorate.png" mode="widthFix"
+          catchtouchstart="rotateStart" catchtouchmove="rotateMove" />
+        <image class="ip-btn ip-btn__scale" src="/assets/img/icon_scale.png" mode="widthFix"
+          catchtouchstart="scaleStart" catchtouchmove="scaleMove" />
         <image class="ip-btn ip-btn__delete" src="/assets/img/icon_delete.png" mode="widthFix" bindtap="deleteIp" />
         <image class="ip-btn ip-btn__delete" src="/assets/img/icon_delete.png" mode="widthFix" bindtap="deleteIp" />
         <image class="ip-btn ip-btn__confirm" src="/assets/img/icon_correct.png" mode="widthFix" bindtap="confirmIp" />
         <image class="ip-btn ip-btn__confirm" src="/assets/img/icon_correct.png" mode="widthFix" bindtap="confirmIp" />
       </view>
       </view>
     </view>
     </view>
 
 
+<!-- 大图模式 -->
     <view wx:else class="w_video fill-img">
     <view wx:else class="w_video fill-img">
       <scroll-view scroll-x="true" class="zoom-scroll" show-scrollbar="{{false}}" bindscroll="onZoomScroll">
       <scroll-view scroll-x="true" class="zoom-scroll" show-scrollbar="{{false}}" bindscroll="onZoomScroll">
         <image class="zoom-image" mode="heightFix" bindload="loadcompele" show-menu-by-longpress="true" src="{{url_link}}" />
         <image class="zoom-image" mode="heightFix" bindload="loadcompele" show-menu-by-longpress="true" src="{{url_link}}" />
@@ -46,20 +79,76 @@
         <image class="ip-btn ip-btn__confirm" src="/assets/img/icon_correct.png" mode="widthFix" bindtap="confirmIp" />
         <image class="ip-btn ip-btn__confirm" src="/assets/img/icon_correct.png" mode="widthFix" bindtap="confirmIp" />
       </view>
       </view>
     </view>
     </view>
+
+    <!-- 编辑模式 -->
     <view wx:if="{{isEditing && projectid == 'ZHS2409020-1'}}" class="ip-list">
     <view wx:if="{{isEditing && projectid == 'ZHS2409020-1'}}" class="ip-list">
-      <scroll-view scroll-y="true" class="ip-scroll" show-scrollbar="{{false}}">
+      <view class="tabList">
+        <view class="tab {{tabIndex==1 ? 'tab_active' : ''}}" bindtap="handleTabTap" data-index="1">贴纸</view>
+        <view class="tab {{tabIndex==2 ? 'tab_active' : ''}}" bindtap="handleTabTap" data-index="2">标题</view>
+        <view class="tab {{tabIndex==3 ? 'tab_active' : ''}}" bindtap="handleTabTap" data-index="3">日期</view>
+      </view>
+      <scroll-view wx:if="{{tabIndex==1}}" scroll-y="true" class="ip-scroll" show-scrollbar="{{false}}">
         <view class="ip-grid">
         <view class="ip-grid">
           <view class="ip-item-wrapper" wx:for="{{ipsImgList}}" wx:key="name" data-index="{{index}}" bindtap="selectIp">
           <view class="ip-item-wrapper" wx:for="{{ipsImgList}}" wx:key="name" data-index="{{index}}" bindtap="selectIp">
-            <image class="ip-item {{selectedIpIndex === index ? 'ip-item__active' : ''}}" src="{{item.imgUrl}}" mode="aspectFit" />
+            <image class="ip-item {{selectedIpIndex === index ? 'ip-item__active' : ''}}" src="{{item.imgUrl}}"
+              mode="aspectFit" />
           </view>
           </view>
         </view>
         </view>
       </scroll-view>
       </scroll-view>
+      <!-- 标题 -->
+      <view class="titleTab" wx:if="{{tabIndex==2}}">
+        <textarea class="titleArea" value="{{title}}" bindinput="onTitleInput" placeholder="请填写标题,最多不超过50个字"
+          maxlength="50" />
+        <view class="colorSelect">
+          <text>字体颜色</text>
+          <view class="color color1 {{rgbIndex==0 ? 'colorAc':''}}" bindtap="setColor" data-index="0"
+            data-rgb="rgba(148, 216, 53, 1)"></view>
+          <view class="color color2 {{rgbIndex==1 ? 'colorAc':''}}" bindtap="setColor" data-index="1"
+            data-rgb="rgba(13, 121, 217, 1)"></view>
+          <view class="color color3 {{rgbIndex==2 ? 'colorAc':''}}" bindtap="setColor" data-index="2"
+            data-rgb="rgba(251, 158, 19, 1)"></view>
+          <view class="color color4 {{rgbIndex==3 ? 'colorAc':''}}" bindtap="setColor" data-index="3"
+            data-rgb="rgba(255, 114, 114, 1)"></view>
+          <view class="colorSelectPick" bindtap="toPick">
+            <image class="selfColor" src="/assets/img/icon_colorPick.png" mode="heightFix" />
+            <text>自定义</text>
+          </view>
+          <color-picker bindchangeColor="pickColor" initColor="{{rgb}}" show="{{pick}}" bindclose="toPick" />
+        </view>
+        <view class="confirmBox">
+          <view class="cConfirm" bindtap="saveTitle" data-index="{{titleDatas.length}}">添加</view>
+        </view>
+      </view>
+      <!-- 日期 -->
+      <view class='dateTab' wx:if="{{tabIndex==3}}">
+        <picker-view class="date-picker" value="{{pickerValue}}" bindchange="onDateChange">
+          <!-- 年份列 -->
+          <picker-view-column>
+            <view class="picker-item" wx:for="{{years}}" wx:key="index">{{item}}年</view>
+          </picker-view-column>
+          <!-- 月份列 -->
+          <picker-view-column>
+            <view class="picker-item" wx:for="{{months}}" wx:key="index">{{item}}月</view>
+          </picker-view-column>
+          <!-- 日期列 -->
+          <picker-view-column>
+            <view class="picker-item" wx:for="{{days}}" wx:key="index">{{item}}日</view>
+          </picker-view-column>
+        </picker-view>
+        <view class="confirmBox">
+          <view class="cConfirm" bindtap="selectIp" data-index="{{0}}">添加</view>
+        </view>
+      </view>
+
     </view>
     </view>
-    <view class="w_btn" style="height:{{info.rescan.height}};background:url({{cdn_url+info.rescan.bg}}) no-repeat center / cover">
-      <view style="padding-left: 20rpx;" bindtap="edit" wx:if="{{projectid == 'ZHS2409020-1' && !isEditing && type!='0'}}">
+    <view class="w_btn"
+      style="height:{{info.rescan.height}};background:url({{cdn_url+info.rescan.bg}}) no-repeat center / cover">
+      <view style="padding-left: 20rpx;" bindtap="edit"
+        wx:if="{{projectid == 'ZHS2409020-1' && !isEditing && type!='0'}}">
         <image src="/assets/img/edit.png" mode="widthFix" />
         <image src="/assets/img/edit.png" mode="widthFix" />
       </view>
       </view>
-      <view class="btn_paise__border" wx:if="{{projectid == 'ZHS2409020-1' && !isEditing && type!='0'}}" style="background:{{info.rescan.borderColor}}" />
+      <view class="btn_paise__border" wx:if="{{projectid == 'ZHS2409020-1' && !isEditing && type!='0'}}"
+        style="background:{{info.rescan.borderColor}}" />
       <view bindtap="cancel">
       <view bindtap="cancel">
         <image src="{{cdn_url}}/images/cancel.png" mode="widthFix" />
         <image src="{{cdn_url}}/images/cancel.png" mode="widthFix" />
       </view>
       </view>
@@ -69,5 +158,6 @@
       </view>
       </view>
     </view>
     </view>
   </view>
   </view>
-  <wxml-to-canvas wx:if="{{widgetVisible}}" class="widget" id="widget" width="{{canvasWidth}}" height="{{canvasHeight}}" style="position: absolute; left: -9999px; top: 0;"></wxml-to-canvas>
-</view>
+  <wxml-to-canvas wx:if="{{widgetVisible}}" class="widget" id="widget" width="{{canvasWidth}}" height="{{canvasHeight}}"
+    style="position: absolute; left: -9999px; top: 0;"></wxml-to-canvas>
+</view>

+ 164 - 2
pages/work/index.wxss

@@ -156,14 +156,151 @@
   left: 0;
   left: 0;
   bottom: 140rpx;
   bottom: 140rpx;
   width: 100%;
   width: 100%;
+  height: 480rpx;
   padding: 20rpx 30rpx;
   padding: 20rpx 30rpx;
   box-sizing: border-box;
   box-sizing: border-box;
-  background: rgba(255, 255, 255, 0.96);
+  background: rgba(255, 255, 255, 1);
   z-index: 10002;
   z-index: 10002;
 }
 }
 
 
+.tabList{
+  width: 100%;
+  height: 65rpx;
+  padding-bottom: 20rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 130rpx;
+  color: rgba(51, 51, 51, 1);
+}
+
+.tabList .tab_active{
+  position: relative;
+  font-weight: bold;
+  color: rgba(13, 121, 217, 1);
+}
+.tabList .tab_active::after{
+  content: "";
+  width: 80%;
+  height: 5rpx;
+  position: absolute;
+  bottom: -8rpx;
+  left: 50%;
+  transform: translateX(-50%);
+  background-color: rgba(13, 121, 217, 1);
+}
+
+.titleTab{
+ max-height: 400rpx;
+ display: flex;
+ flex-direction: column;
+ gap: 10rpx;
+}
+.titleTab .titleArea{
+  width: 100%;
+  height: 160rpx;
+  padding: 30rpx 30rpx;
+  border: 1rpx solid rgba(217, 217, 217, 1);
+  border-radius: 10rpx;
+  box-sizing: border-box;
+  font-size: 24rpx;
+}
+.titleTab .titleArea::placeholder{
+  color: rgba(217, 217, 217, 1);
+}
+.titleTab .colorSelect{
+  width: 100%;
+  height: 80rpx;
+  display: flex;
+  align-items: center;
+  font-size: 26rpx;
+  color: rgba(0, 0, 0, 1);
+  gap: 30rpx;
+}
+.titleTab .colorSelect .color{
+  width: 45rpx;
+  height: 45rpx;
+  border-radius: 50%;
+}
+.titleTab .colorSelect .color1{
+  background-color:rgba(148, 216, 53, 1) ;
+}
+.titleTab .colorSelect .color2{
+  background-color:rgba(13, 121, 217, 1) ;
+}
+.titleTab .colorSelect .color3{
+  background-color:rgba(251, 158, 19, 1) ;
+}
+.titleTab .colorSelect .color4{
+  background-color:rgba(255, 114, 114, 1);
+}
+.titleTab .colorSelect .colorAc{
+  box-shadow: 0rpx 8rpx 13rpx 2rpx rgba(0,0,0,0.25);
+}
+.titleTab .colorSelect .colorSelectPick{
+  width: 170rpx;
+  height: 45rpx;
+  display: flex;
+  align-items: center;
+  gap: 14rpx;
+}
+.titleTab .colorSelect .colorSelectPick .selfColor{
+  width: 45rpx;
+  height: 45rpx;
+}
+ .confirmBox{
+  width: 100%;
+  height: 60rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 20rpx;
+  margin-top: 16rpx;
+}
+ .confirmBox .cCancel,.cConfirm{
+  width: 47%;
+  height: 80rpx;
+  font-size: 28rpx;
+  border-radius: 10rpx;
+  border: 1px solid rgba(13, 121, 217, 1);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: rgba(13, 121, 217, 1);
+}
+.confirmBox .cConfirm{
+  background-color: rgba(13, 121, 217, 1);
+  color:rgba(255, 255, 255, 1) ;
+}
+.dateTab{
+  max-height: 400rpx;
+ display: flex;
+ flex-direction: column;
+ gap: 20rpx;
+}
+.picker-title {
+  font-size: 32rpx;
+  color: rgba(0, 0, 0, 1);
+  margin-bottom: 20rpx;
+}
+/* 日期选择器样式 */
+.date-picker {
+  box-sizing: border-box;
+  padding:10rpx 40rpx ;
+  width: 100%;
+  height: 240rpx;
+  border-radius: 16rpx;
+}
+
+/* 选项文字样式 */
+.picker-item {
+  text-align: center;
+  font-size: 36rpx;
+  color: #333;
+  line-height: 80rpx;
+}
 .ip-scroll {
 .ip-scroll {
-  max-height: 300rpx;
+  max-height: 360rpx;
 }
 }
 
 
 .ip-grid {
 .ip-grid {
@@ -199,10 +336,35 @@
   z-index: 10001;
   z-index: 10001;
 }
 }
 
 
+.ip-overlay-title{
+  padding: 30rpx;
+  position: absolute;
+  right: 40rpx;
+  bottom: 40rpx;
+  width: fit-content;
+  height: fit-content;
+ max-width: 100%;
+  z-index: 10001;
+}
+
 .ip-main {
 .ip-main {
   width: 240rpx;
   width: 240rpx;
   height: 240rpx;
   height: 240rpx;
 }
 }
+.ip-title-main{
+  font-family: TsangerXWZ, TsangerXWZ;
+  background-color: rgba(156, 208, 255, 1);
+  border-radius: 20px;
+  text-align: center;
+  font-size: 35rpx;
+  padding: 10rpx;
+  width: 100%;
+  min-width: 140rpx;
+  height: fit-content;
+  line-height: 55rpx;
+  min-height: 60rpx;
+  word-break: break-all;
+}
 
 
 .ip-btn {
 .ip-btn {
   position: absolute;
   position: absolute;

+ 78 - 78
project.config.json

@@ -1,79 +1,79 @@
-{
-  "description": "项目配置文件,详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
-  "setting": {
-    "urlCheck": false,
-    "es6": true,
-    "enhance": false,
-    "postcss": true,
-    "preloadBackgroundData": false,
-    "minified": true,
-    "newFeature": false,
-    "coverView": true,
-    "nodeModules": false,
-    "autoAudits": false,
-    "showShadowRootInWxmlPanel": true,
-    "scopeDataCheck": false,
-    "uglifyFileName": false,
-    "checkInvalidKey": true,
-    "checkSiteMap": true,
-    "uploadWithSourceMap": true,
-    "compileHotReLoad": false,
-    "lazyloadPlaceholderEnable": false,
-    "useMultiFrameRuntime": true,
-    "babelSetting": {
-      "ignore": [],
-      "disablePlugins": [],
-      "outputPath": ""
-    },
-    "useIsolateContext": false,
-    "userConfirmedBundleSwitch": false,
-    "packNpmManually": false,
-    "packNpmRelationList": [],
-    "minifyWXSS": true,
-    "disableUseStrict": false,
-    "minifyWXML": true,
-    "showES6CompileOption": false,
-    "useCompilerPlugins": false,
-    "ignoreUploadUnusedFiles": true,
-    "useStaticServer": true,
-    "condition": false,
-    "useApiHook": false,
-    "useApiHostProcess": false,
-    "compileWorklet": false,
-    "localPlugins": false,
-    "swc": false,
-    "disableSWC": true
-  },
-  "compileType": "miniprogram",
-  "condition": {
-    "search": {
-      "list": []
-    },
-    "conversation": {
-      "list": []
-    },
-    "game": {
-      "list": []
-    },
-    "plugin": {
-      "list": []
-    },
-    "gamePlugin": {
-      "list": []
-    },
-    "miniprogram": {
-      "list": []
-    }
-  },
-  "editorSetting": {
-    "tabIndent": "insertSpaces",
-    "tabSize": 2
-  },
-  "libVersion": "3.12.1",
-  "packOptions": {
-    "ignore": [],
-    "include": []
-  },
-  "appid": "wx5f90253f7065fb77",
-  "simulatorPluginLibVersion": {}
+{
+  "description": "项目配置文件,详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
+  "setting": {
+    "urlCheck": false,
+    "es6": true,
+    "enhance": false,
+    "postcss": true,
+    "preloadBackgroundData": false,
+    "minified": true,
+    "newFeature": false,
+    "coverView": true,
+    "nodeModules": false,
+    "autoAudits": false,
+    "showShadowRootInWxmlPanel": true,
+    "scopeDataCheck": false,
+    "uglifyFileName": false,
+    "checkInvalidKey": true,
+    "checkSiteMap": true,
+    "uploadWithSourceMap": true,
+    "compileHotReLoad": false,
+    "lazyloadPlaceholderEnable": false,
+    "useMultiFrameRuntime": true,
+    "babelSetting": {
+      "ignore": [],
+      "disablePlugins": [],
+      "outputPath": ""
+    },
+    "useIsolateContext": false,
+    "userConfirmedBundleSwitch": false,
+    "packNpmManually": false,
+    "packNpmRelationList": [],
+    "minifyWXSS": true,
+    "disableUseStrict": false,
+    "minifyWXML": true,
+    "showES6CompileOption": false,
+    "useCompilerPlugins": false,
+    "ignoreUploadUnusedFiles": true,
+    "useStaticServer": true,
+    "condition": false,
+    "useApiHook": false,
+    "useApiHostProcess": false,
+    "compileWorklet": false,
+    "localPlugins": false,
+    "swc": false,
+    "disableSWC": true
+  },
+  "compileType": "miniprogram",
+  "condition": {
+    "search": {
+      "list": []
+    },
+    "conversation": {
+      "list": []
+    },
+    "game": {
+      "list": []
+    },
+    "plugin": {
+      "list": []
+    },
+    "gamePlugin": {
+      "list": []
+    },
+    "miniprogram": {
+      "list": []
+    }
+  },
+  "editorSetting": {
+    "tabIndent": "insertSpaces",
+    "tabSize": 2
+  },
+  "libVersion": "3.12.1",
+  "packOptions": {
+    "ignore": [],
+    "include": []
+  },
+  "appid": "wx5f90253f7065fb77",
+  "simulatorPluginLibVersion": {}
 }
 }